package main

import (
	"archive/zip"
	"bytes"
	"crypto/md5"
	"flag"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"mime/multipart"
	"net/http"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"syscall"
	"time"
)

func main() {
	// -auth="YmdHis" -url="http://127.0.0.1:8000/upload"
	auth := flag.String("auth", time.Now().Format("20060102150405"), "密钥")
	url := flag.String("url", "http://127.0.0.1:8000/upload", "上传地址")
	flag.Parse()

	path := "C:\\Users\\" + GetUserName() + "\\Documents\\SiYuan\\data"

	// 是否存在备份目录
	exists, _ := PathExists(path)
	if !exists {
		fmt.Println("笔记目录不存在")
		os.Exit(0)
	}

	// 是否还未关闭软件
	isRunning := IsExeRunning("SiYuan", "\\SiYuan.exe")
	if isRunning {
		fmt.Println("思源笔记进程[SiYuan.exe]在运行中，请关闭后重试")
		os.Exit(0)
	}

	// 打包目录
	f, _ := os.Open(path)
	defer f.Close()
	var files = []*os.File{f}

	zipDist := path + ".zip"
	err := Compress(files, zipDist)
	if err != nil {
		fmt.Println(err)
		os.Exit(0)
	}

	fmt.Println("成功备份：" + zipDist)

	md5, _ := Md5File(zipDist)

	extraParams := map[string]string{
		"auth": *auth,
		"md5":  md5,
	}
	request, err := UploadRequest(
		*url,
		extraParams,
		"sydata",
		zipDist)
	if err != nil {
		log.Fatal(err)
	}
	client := &http.Client{}
	resp, err := client.Do(request)
	if err != nil {
		log.Fatal(err)
	} else {
		body := &bytes.Buffer{}
		_, err := body.ReadFrom(resp.Body)
		if err != nil {
			log.Fatal(err)
		}
		resp.Body.Close()
		fmt.Println(body)
	}
}

// UploadRequest 发起上传文件请求
func UploadRequest(uri string, params map[string]string, paramName, path string) (*http.Request, error) {
	file, err := os.Open(path)
	if err != nil {
		return nil, err
	}
	defer file.Close()

	body := &bytes.Buffer{}
	writer := multipart.NewWriter(body)
	part, err := writer.CreateFormFile(paramName, filepath.Base(path))
	if err != nil {
		return nil, err
	}
	_, err = io.Copy(part, file)

	for key, val := range params {
		_ = writer.WriteField(key, val)
	}
	err = writer.Close()
	if err != nil {
		return nil, err
	}

	request, err := http.NewRequest("POST", uri, body)
	request.Header.Add("Content-Type", writer.FormDataContentType())
	return request, err
}

// PathExists 目录是否存在
func PathExists(path string) (bool, error) {
	_, err := os.Stat(path)
	if err == nil {
		return true, nil
	}
	if os.IsNotExist(err) {
		return false, nil
	}
	return false, err
}

// IsExeRunning 进程是否在运行中
func IsExeRunning(strKey string, strExeName string) bool {
	buf := bytes.Buffer{}
	cmd := exec.Command("wmic", "process", "get", "name,executablepath")
	cmd.Stdout = &buf
	err := cmd.Run()
	if err != nil {
		// 执行失败
		return false
	}

	cmd2 := exec.Command("findstr", strKey)
	cmd2.Stdin = &buf
	data, err := cmd2.CombinedOutput()
	if err != nil && err.Error() != "exit status 1" {
		//XBLog.LogF("ServerMonitor", "IsExeRunning CombinedOutput error, err:%s", err.Error())
		return false
	}

	strData := string(data)

	if strings.Contains(strData, strExeName) {
		return true
	} else {
		return false
	}
}

// Compress 压缩文档
func Compress(files []*os.File, dest string) error {
	d, _ := os.Create(dest)
	defer d.Close()
	w := zip.NewWriter(d)
	defer w.Close()
	for _, file := range files {
		err := compress(file, "", w)
		if err != nil {
			return err
		}
	}
	return nil
}

func compress(file *os.File, prefix string, zw *zip.Writer) error {
	info, err := file.Stat()
	if err != nil {
		return err
	}
	if info.IsDir() {
		prefix = prefix + "/" + info.Name()
		fileInfos, err := file.Readdir(-1)
		if err != nil {
			return err
		}
		for _, fi := range fileInfos {
			f, err := os.Open(file.Name() + "/" + fi.Name())
			if err != nil {
				return err
			}
			err = compress(f, prefix, zw)
			if err != nil {
				return err
			}
		}
	} else {
		header, err := zip.FileInfoHeader(info)
		header.Name = prefix + "/" + header.Name
		if err != nil {
			return err
		}
		writer, err := zw.CreateHeader(header)
		if err != nil {
			return err
		}
		_, err = io.Copy(writer, file)
		file.Close()
		if err != nil {
			return err
		}
	}
	return nil
}

// GetUserName 获取当前用户名
func GetUserName() string {
	var size uint32 = 128
	var buffer = make([]uint16, size)
	user := syscall.StringToUTF16Ptr("USERNAME")

	_, err := syscall.GetEnvironmentVariable(user, &buffer[0], size)
	if err != nil {
		return ""
	}

	return syscall.UTF16ToString(buffer)
}

// Md5File 计算文件 MD5 值
func Md5File(file string) (value string, err error) {
	data, err := ioutil.ReadFile(file)
	if err != nil {
		return
	}

	md := md5.Sum(data)
	value = fmt.Sprintf("%x\n", md)
	return
}
